﻿using System;
using System.Linq;
using System.Net.NetworkInformation;
using Kaleida.ServiceMonitor.Framework;
using Kaleida.ServiceMonitor.Framework.Responses;

namespace Kaleida.ServiceMonitor.Operations.PreparedRequests
{
    public class Ping : PreparedRequest
    {
    	private const int SendBufferSize = 32;
        private TimeSpan timeout;

        private readonly string hostNameOrAddress;

        public Ping(string hostNameOrAddress) : this(hostNameOrAddress, "50s")
        {
        }
        public Ping(string hostNameOrAddress, string timeout)
        {
            var timeoutTimeSpan = timeout.ToTimeSpan();

            if (timeoutTimeSpan.TotalMilliseconds < 1)
                throw new ArgumentOutOfRangeException("timeout", timeout, "Cannot be less than 1 millisecond");

            this.hostNameOrAddress = hostNameOrAddress;
            this.timeout = timeoutTimeSpan;
        }

        public override string Description
        {
			get { return string.Format("Determine if '{0}' is available using an ICMP request containing {1} bytes and a timeout of {2}ms", hostNameOrAddress, SendBufferSize, timeout.TotalMilliseconds); }
        }

        public override object GetResponse()
        {
            var pinger = new System.Net.NetworkInformation.Ping();
			byte[] sendBuffer = BuildData(SendBufferSize);

            PingReply reply = pinger.Send(hostNameOrAddress, (int) timeout.TotalMilliseconds, sendBuffer);

			if (reply == null)
				throw new InvalidOperationException("Ping reply was null");

			return ProcessReply(hostNameOrAddress, sendBuffer, reply.Status, reply.Buffer, reply.RoundtripTime);
        }

		internal static PingResponse ProcessReply(string hostNameOrAddress, byte[] buffer, IPStatus replyStatus, byte[] replyBuffer, long roundTripTimeMs)
    	{
			switch (replyStatus)
    		{
    			case IPStatus.Success:
    				if (!replyBuffer.SequenceEqual(buffer))
    					return new PingResponse(new PingReplyUnexpectedBufferException(buffer, replyBuffer));

    				return new PingResponse(TimeSpan.FromMilliseconds(roundTripTimeMs));
    			case IPStatus.TimedOut:
    				return new PingResponse(new PingTimedOutException(string.Format("Ping to {0} timed out", hostNameOrAddress)));
    			default:
					return new PingResponse(new InvalidOperationException(string.Format("Ping to {0} returned {1}", hostNameOrAddress, replyStatus)));
    		}
    	}

    	internal static byte[] BuildData(int length)
    	{
    		return Enumerable.Range(0, length).Select(i => (byte) (((int) 'a') + (i % 23))).ToArray();
		}
    }
}
